﻿//LogManager.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

using System.Collections.Concurrent;

namespace StockSharp.Logging
{
	using System;
	using System.Collections.Generic;
	using System.Threading;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Configuration;

	/// <summary>
	/// Менеджер логирования сообщений, который мониторит событие <see cref="ILogSource.Log"/> и перенаправляет сообщения в <see cref="Listeners"/>.
	/// </summary>
	public class LogManager : Disposable
	{
		private sealed class ApplicationReceiver : BaseLogReceiver<ApplicationReceiver>
		{
			public ApplicationReceiver()
			{
				Name = TypeHelper.ApplicationName;
				LogLevel = LogLevels.Info;
			}
		}

		private sealed class LogSourceList : BaseList<ILogSource>
		{
			private readonly LogManager _parent;

			public LogSourceList(LogManager parent)
			{
				if (parent == null)
					throw new ArgumentNullException("parent");

				_parent = parent;
			}

			protected override bool OnAdding(ILogSource item)
			{
				item.Log += _parent.SourceLog;
				return base.OnAdding(item);
			}

			protected override bool OnRemoving(ILogSource item)
			{
				item.Log -= _parent.SourceLog;
				return base.OnRemoving(item);
			}

			protected override bool OnClearing()
			{
				foreach (var item in this)
					OnRemoving(item);

				return base.OnClearing();
			}
		}

		private readonly ConcurrentQueue<LogMessage> _pendingMessages = new ConcurrentQueue<LogMessage>();
		private readonly Timer _flushTimer;


		/// <summary>
		/// Создать <see cref="LogManager"/>.
		/// </summary>
		public LogManager()
		{
			ConfigManager.TryRegisterService(this);

			Sources = new LogSourceList(this)
			{
				Application,
				new UnhandledExceptionSource()
			};

			_flushTimer = ThreadingHelper.Timer(Flush);
			FlushInterval = TimeSpan.FromMilliseconds(500);
		}

		private bool _isFlusing;
		private void Flush()
		{
			if (_isFlusing) return;
			try
			{
				// операция не критичная. lock не нужен. Коллекция обеспечивает свою безопасность сама.
				_isFlusing = true;
				var msgs = new List<LogMessage>();
				LogMessage message;
				while (_pendingMessages.TryDequeue(out message))
				{
					msgs.Add(message);
				}

				if (msgs.Count == 0)
					return;

				_listeners.Cache.ForEach(l => l.WriteMessages(msgs));
			}
			finally
			{
				_isFlusing = false;
			}

		}

		private ILogReceiver _application = new ApplicationReceiver();

		/// <summary>
		/// Получатель логов уровня всего приложения.
		/// </summary>
		public ILogReceiver Application
		{
			get { return _application; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				_application = value;
			}
		}

		private readonly CachedSynchronizedSet<ILogListener> _listeners = new CachedSynchronizedSet<ILogListener>();

		/// <summary>
		/// Логгеры сообщений, приходящие от <see cref="Sources"/>.
		/// </summary>
		public IList<ILogListener> Listeners
		{
			get { return _listeners; }
		}

		/// <summary>
		/// Источники логов, у которых слушается событие <see cref="ILogSource.Log"/>.
		/// </summary>
		public IList<ILogSource> Sources { get; private set; }

		/// <summary>
		/// Интервал передачи накопленных от <see cref="Sources"/> сообщений в <see cref="Listeners"/>.
		/// По-умолчанию равно 500 млс.
		/// </summary>
		public TimeSpan FlushInterval
		{
			get { return _flushTimer.Interval(); }
			set
			{
				if (value <= TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("value", value, "Интервал должен быть положительным.");

				_flushTimer.Interval(value);
			}
		}

		private int _maxMessageCount = 1000;

		/// <summary>
		/// Максимальное количество накопленных от <see cref="Sources"/> сообщений, прежде чем они будут отправлены в <see cref="Listeners"/>.
		/// По умолчанию равно 1000.
		/// </summary>
		/// <remarks>Значение 0 означает бесконечные размер буфера.</remarks>
		public int MaxMessageCount
		{
			get { return _maxMessageCount; }
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, "Количество не может быть отрицательным.");

				_maxMessageCount = value;
			}
		}

		private void SourceLog(LogMessage message)
		{
			if (message == null)
				throw new ArgumentNullException("message");

			if (GetSourceLevel(message.Source) > message.Level)
				return;

			_pendingMessages.Enqueue(message);
			bool needFlush = MaxMessageCount > 0 && _pendingMessages.Count > MaxMessageCount;

			if (needFlush)
				_flushTimer.Change(0, FlushInterval.Milliseconds);
		}

		private LogLevels GetSourceLevel(ILogSource source)
		{
			var level = source.LogLevel;

			if (level != LogLevels.Inherit)
				return level;

			var parent = source.Parent;
			return parent != null ? GetSourceLevel(parent) : Application.LogLevel;
		}

		/// <summary>
		/// Освободить ресурсы.
		/// </summary>
		protected override void DisposeManaged()
		{
			Sources.Clear();
			_flushTimer.Dispose();

			_listeners.SyncDo(c =>
			{
				c.ForEach(l => l.DoDispose());
				c.Clear();
			});

			base.DisposeManaged();
		}
	}
}